ARISE AFRICA NETWORK VOCATIONAL COURSE (AAN)

Module 7 — Advanced JavaScript: Arrays, Functions & Modules (Expanded)

Introduction & Learning Goals

This expanded module gives you mastery over arrays, functions, and modular architecture in JavaScript. We'll move from fundamentals to advanced patterns used in production: higher-order functions, closures, immutability, pure functions, performance considerations, and module design. The practical outcome is a robust TODO application structured into modules with tests, persistent storage, and clear separation of concerns.

By the end of this lesson you will be able to:

Key Terms & Definitions

Closure
A function together with the lexical environment in which it was declared — it "remembers" outer variables.
Higher-order function
A function that accepts other functions as arguments or returns a function.
Pure function
Function that returns a value based only on its inputs and has no side effects.
Immutability
Pattern of not changing data in place; instead return new copies when transformations are needed.
Map/Filter/Reduce
Core array methods for transforming, selecting and aggregating data.
Event delegation
Attach a single event listener to a parent to handle events from many children efficiently.
Module
Self-contained file or unit exposing an API via exports and hiding internal details.
Pure side-effect
Operations like network calls or DOM mutations — keep these at the edges of your app.
Memoization
Caching function results to avoid recomputation.
Debounce
Delay function execution until a pause in events.

Theory — Functions, Arrays & Patterns

Closures and Scope

Closures are a foundational concept. A function defined inside another function has access to the outer function's variables even after the outer function returns. This is powerful for data encapsulation and factories.

// Counter factory using closure
function createCounter(initial = 0){
  let count = initial;
  return {
    increment(){ count++; return count; },
    decrement(){ count--; return count; },
    value(){ return count; }
  };
}
const c = createCounter(5);
console.log(c.increment()); // 6

Note how the internal count is not directly accessible and only mutated via the returned methods. This pattern avoids global state and accidental mutation.

Higher-order functions and composition

Functions that accept other functions promote reusability. Compose small functions to create more complex behavior:

const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const add1 = x => x + 1;
const double = x => x * 2;
const result = pipe(add1, double)(3); // (3+1)*2 = 8

Array methods — map, filter, reduce

Use map to transform arrays, filter to select items, and reduce for aggregation:

const nums = [1,2,3,4];
const squares = nums.map(n => n*n); // [1,4,9,16]
const evens = nums.filter(n => n % 2 === 0); // [2,4]
const sum = nums.reduce((acc,n) => acc + n, 0); // 10

Avoid side-effects inside these methods; prefer returning new values.

Immutability & Pure Functions

Immutability reduces bugs. Instead of mutating arrays with push or splice, use spread and slice to return new arrays.

const arr = [1,2,3];
const arr2 = [...arr, 4]; // [1,2,3,4]
const removed = arr2.filter(x => x !== 2); // [1,3,4]

Event delegation & efficient DOM updates

Instead of adding listeners to many elements, attach a listener to a parent and use event.target to determine the source. For frequent updates, batch DOM changes using DocumentFragment or re-render ranges only instead of full replace.

Modules & organizing code

Split your app into modules: storage.js (persistence), ui.js (rendering), app.js (controller). Use ES modules (export/import) to keep code testable. Keep side-effects at the top-level controller to make modules pure where possible.

Practical Project — Modular TODO App (Overview)

We will build a TODO app split into three modules in a single HTML playground for offline use:

This separation makes unit testing straightforward and reduces bugs.

Playground — TODO App Starter

Edit and run the app in the preview. The starter is modular and written inline for simplicity; in production split into separate files and import as modules.

Practical Labs — Progressive Steps

Lab 1 — Refactor model functions

  1. Write unit tests (simple functions) to assert add/remove/toggle behaviors using plain assertions or a lightweight test helper.
  2. Ensure functions are pure: no side-effects and always return new arrays.

Lab 2 — Improve performance for large lists

  1. When dealing with thousands of items, avoid re-rendering the entire list. Only update the item changed or use virtualization techniques.
  2. Use DocumentFragment as shown, and avoid innerHTML for large updates.

Lab 3 — Add filtering and derived views

  1. Add buttons for All, Active, Completed. Implement model.filterViews(todos, view) pure function returning filtered lists.
  2. Cache derived results where appropriate and invalidate cache on mutations.

Lab 4 — Modular testing and CI integration

  1. Extract pure logic into a file model.test.js and create simple test runner or use a framework when available.
  2. Integrate a basic GitHub Action to run tests on push to main.

Advanced Techniques

Memoization

Memoize heavy computations to avoid repeated work. Simple example:

function memoize(fn){
  const cache = new Map();
  return function(arg){
    if(cache.has(arg)) return cache.get(arg);
    const res = fn(arg); cache.set(arg, res); return res;
  };
}

Batching updates & Debounce

Debounce user input to prevent frequent model operations; batch DOM updates to limit reflows.

function debounce(fn, wait=300){
  let t; return (...args) => { clearTimeout(t); t = setTimeout(()=> fn(...args), wait); };
}

Error handling and validation

Validate inputs at the boundaries (UI) and in the model. Use try/catch around operations that could throw and provide clear user messages.

Faith Corner

"Two are better than one…" — Ecclesiastes 4:9. Collaborate, review each other's code, and mentor juniors — teamwork multiplies results.

Knowledge Check — 20 Questions

  1. Q1. What is a closure?


  2. Q2. Which method transforms every element in an array?


  3. Q3. What should a pure function avoid?


  4. Q4. Which array method reduces array to single value?


  5. Q5. What does event delegation help with?


  6. Q6. How to create a new array with an added item without mutating?


  7. Q7. Which is a higher-order function?


  8. Q8. What is memoization used for?


  9. Q9. Which is recommended for large lists?


  10. Q10. What is the advantage of pure functions?


  11. Q11. Which creates a shallow copy of an object?


  12. Q12. Debounce helps to:


  13. Q13. Which method finds first matching element?


  14. Q14. Where should side-effects be kept?


  15. Q15. Which is true about ES modules?


  16. Q16. How to toggle done status immutably?


  17. Q17. What does DocumentFragment help with?


  18. Q18. Which is true about reduce?


  19. Q19. Good test for model functions includes:


  20. Q20. Which helps avoid accidental mutation in arrays?